#include "config.h"
#include <glib-unix.h>
+#include <sys/statvfs.h>
#include <gio/gfiledescriptorbased.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
else
size = 0;
+ /* Free space check; only applies during transactions */
+ if (self->min_free_space_percent > 0 && self->in_transaction)
+ {
+ g_mutex_lock (&self->txn_stats_lock);
+ g_assert_cmpint (self->txn_blocksize, >, 0);
+ const fsblkcnt_t object_blocks = (size / self->txn_blocksize) + 1;
+ if (object_blocks > self->max_txn_blocks)
+ {
+ g_mutex_unlock (&self->txn_stats_lock);
+ g_autofree char *formatted_required = g_format_size ((guint64)object_blocks * self->txn_blocksize);
+ return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s more required",
+ self->min_free_space_percent, formatted_required);
+ }
+ /* This is the main bit that needs mutex protection */
+ self->max_txn_blocks -= object_blocks;
+ g_mutex_unlock (&self->txn_stats_lock);
+ }
+
/* For regular files, we create them with default mode, and only
* later apply any xattrs and setuid bits. The rationale here
* is that an attacker on the network with the ability to MITM
memset (&self->txn_stats, 0, sizeof (OstreeRepoTransactionStats));
self->in_transaction = TRUE;
+ if (self->min_free_space_percent > 0)
+ {
+ struct statvfs stvfsbuf;
+ if (TEMP_FAILURE_RETRY (fstatvfs (self->repo_dir_fd, &stvfsbuf)) < 0)
+ return glnx_throw_errno_prefix (error, "fstatvfs");
+ g_mutex_lock (&self->txn_stats_lock);
+ self->txn_blocksize = stvfsbuf.f_bsize;
+ /* Convert fragment to blocks to compute the total */
+ guint64 total_blocks = (stvfsbuf.f_frsize * stvfsbuf.f_blocks) / stvfsbuf.f_bsize;
+ /* Use the appropriate free block count if we're unprivileged */
+ guint64 bfree = (getuid () != 0 ? stvfsbuf.f_bavail : stvfsbuf.f_bfree);
+ guint64 reserved_blocks = ((double)total_blocks) * (self->min_free_space_percent/100.0);
+ if (bfree > reserved_blocks)
+ self->max_txn_blocks = bfree - reserved_blocks;
+ else
+ {
+ g_mutex_unlock (&self->txn_stats_lock);
+ g_autofree char *formatted_free = g_format_size (bfree * self->txn_blocksize);
+ return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s available",
+ self->min_free_space_percent, formatted_free);
+ }
+ g_mutex_unlock (&self->txn_stats_lock);
+ }
gboolean ret_transaction_resume = FALSE;
if (!_ostree_repo_allocate_tmpdir (self->tmp_dir_fd,
#include <locale.h>
#include <glib/gstdio.h>
#include <sys/file.h>
+#include <sys/statvfs.h>
/**
* SECTION:ostree-repo
self->zlib_compression_level = OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL;
}
+ { g_autofree char *min_free_space_percent_str = NULL;
+ /* If changing this, be sure to change the man page too */
+ const char *default_min_free_space = "3";
+
+ if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-percent",
+ default_min_free_space,
+ &min_free_space_percent_str, error))
+ return FALSE;
+
+ self->min_free_space_percent = g_ascii_strtoull (min_free_space_percent_str, NULL, 10);
+ if (self->min_free_space_percent > 99)
+ return glnx_throw (error, "Invalid min-free-space-percent '%s'", min_free_space_percent_str);
+ }
+
{
g_clear_pointer (&self->collection_id, g_free);
if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id",
--- /dev/null
+#!/bin/bash
+# Test min-free-space-percent using loopback devices
+
+set -xeuo pipefail
+
+dn=$(dirname $0)
+. ${dn}/libinsttest.sh
+
+test_tmpdir=$(prepare_tmpdir)
+trap _tmpdir_cleanup EXIT
+
+cd ${test_tmpdir}
+truncate -s 100MB testblk.img
+blkdev=$(losetup --find --show $(pwd)/testblk.img)
+mkfs.xfs ${blkdev}
+mkdir mnt
+mount ${blkdev} mnt
+ostree --repo=mnt/repo init --mode=bare-user
+if ostree --repo=mnt/repo pull-local /ostree/repo ${host_commit} 2>err.txt; then
+ fatal "succeeded in doing a pull with no free space"
+fi
+assert_file_has_content err.txt "min-free-space-percent"
+umount mnt
+losetup -d ${blkdev}